package com.example.demo.generator.scaffold.service;

import com.example.demo.generator.scaffold.model.GeneratedFile;
import com.example.demo.generator.scaffold.model.TemplateContext;
import com.example.demo.generator.scaffold.model.TemplateDescriptor;
import com.example.demo.generator.scaffold.util.ScaffoldNameUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import com.example.demo.common.ErrorCode;
import com.example.demo.exception.BusinessException;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.example.demo.generator.codegen.model.FieldInfo;
import com.example.demo.generator.codegen.model.IndexInfo;

/**
 * 封装 Freemarker 渲染逻辑，后续可扩展输出到文件或压缩包。
 */
@Service
public class TemplateRenderService {

    private final Configuration configuration;

    public TemplateRenderService() {
        this.configuration = new Configuration(Configuration.VERSION_2_3_32);
        this.configuration.setDefaultEncoding(StandardCharsets.UTF_8.name());
        this.configuration.setClassicCompatible(true);
        this.configuration.setLocalizedLookup(false);
        this.configuration.setLogTemplateExceptions(false);
        this.configuration.setClassLoaderForTemplateLoading(
                TemplateRenderService.class.getClassLoader(),
                ""
        );
    }

    /**
     * 渲染模板并返回字符串结果。
     *
     * @param descriptor 模板描述信息
     * @param context    上下文参数
     * @return 渲染结果
     */
    public String renderToString(TemplateDescriptor descriptor, TemplateContext context) {
        return render(descriptor, context).getContent();
    }

    public GeneratedFile render(TemplateDescriptor descriptor, TemplateContext context) {
        try {
            Template template = configuration.getTemplate(descriptor.getTemplatePath());
            Map<String, Object> dataModel = buildDataModel(context);
            try (StringWriter writer = new StringWriter()) {
                template.process(dataModel, writer);
                String content = writer.toString();
                return GeneratedFile.builder()
                        .templateId(descriptor.getId())
                        .relativePath(descriptor.resolveOutputPath(context))
                        .content(content)
                        .build();
            }
        } catch (IOException | TemplateException e) {
            throw new BusinessException(ErrorCode.CODE_GENERATE_ERROR, "模板渲染失败: " + descriptor.getTemplatePath() + " -> " + e.getMessage());
        }
    }

    /**
     * 预留：检测模板是否存在，供调用方进行快速校验。
     */
    public boolean exists(String templatePath) {
        return new ClassPathResource(templatePath).exists();
    }

    private Map<String, Object> buildDataModel(TemplateContext context) {
        Map<String, Object> model = new HashMap<>();
        model.put("moduleName", context.getModuleName());
        model.put("basePackage", context.getBasePackage());
        model.put("modulePackage", context.getModulePackage());
        model.put("modulePath", context.getModulePath());
        model.put("moduleBasePath", context.getModuleBasePath());
        model.put("moduleSimpleName", context.getModuleSimpleName());
        model.put("classInfo", context.getClassInfo());
        model.put("primaryKeyColumns", context.getClassInfo().getPrimaryKeyColumns());
        model.put("primaryKeyFieldNames", resolvePrimaryKeyFieldNames(context));
        model.put("uniqueFieldNames", resolveUniqueFieldNames(context));
        List<FieldInfo> primaryKeyFields = context.getClassInfo().getFieldList().stream()
                .filter(FieldInfo::isPrimaryKey)
                .collect(Collectors.toList());
        model.put("primaryKeyFields", primaryKeyFields);
        model.put("hasCompositePrimaryKey", primaryKeyFields.size() > 1);
        List<IndexInfo> compositeUniqueIndexes = context.getClassInfo().getIndexList().stream()
                .filter(index -> index != null && index.isUnique() && index.getColumnNames().size() > 1)
                .collect(Collectors.toList());
        model.put("compositeUniqueIndexes", compositeUniqueIndexes);
        model.put("hasCompositeUniqueIndex", !compositeUniqueIndexes.isEmpty());
        ValidationHelper validationHelper = ValidationHelper.INSTANCE;
        model.put("validationHelper", validationHelper);
        model.put("createValidationImports", validationHelper.collectImports(context.getClassInfo().getFieldList(), true));
        model.put("updateValidationImports", validationHelper.collectImports(context.getClassInfo().getFieldList(), false));
        model.put("rawCreateTableSql", context.getRawCreateTableSql());
        model.put("options", context.getOptions());
        model.put("generatedAt", context.getGeneratedAt());
        model.put("generatedAtFormatted", context.getGeneratedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        model.put("frontendFramework", context.getFrontendFramework());
        String apiBasePath = ScaffoldNameUtils.deriveApiBasePath(context);
        model.put("apiBasePath", apiBasePath);
        model.put("frontendProjectRoot", ScaffoldNameUtils.deriveFrontendProjectName(context));
        String routeSegment = ScaffoldNameUtils.deriveFrontendRouteSegment(context);
        model.put("frontendRoutePath", routeSegment);
        model.put("frontendRouteFullPath", "/" + routeSegment);
        model.put("frontendManagementBaseName", ScaffoldNameUtils.deriveFrontendManagementBaseName(context));
        if (context.getFrontendFramework() == com.example.demo.generator.scaffold.model.ScaffoldOptions.FrontendFramework.VUE) {
            String component = ScaffoldNameUtils.deriveFrontendVueViewComponentName(context);
            model.put("frontendPageComponentName", component);
            model.put("frontendPageFileBase", component);
        } else {
            String component = ScaffoldNameUtils.deriveFrontendReactPageComponentName(context);
            model.put("frontendPageComponentName", component);
            model.put("frontendPageFileBase", component);
        }

        String tableComment = extractTableComment(context.getRawCreateTableSql());
        String moduleDisplayName = resolveModuleDisplayName(context);
        String moduleSummary = resolveModuleSummary(context, moduleDisplayName, tableComment);
        model.put("moduleDisplayName", moduleDisplayName);
        model.put("moduleSummary", moduleSummary);
        model.put("primaryKeyFieldName", ScaffoldNameUtils.resolvePrimaryKeyFieldName(context));
        model.put("primaryKeyFieldType", ScaffoldNameUtils.resolvePrimaryKeyFieldType(context));
        Map<String, String> operationVerbs = new HashMap<>();
        operationVerbs.put("get", ScaffoldNameUtils.resolveOperationVerb("GET"));
        operationVerbs.put("post", ScaffoldNameUtils.resolveOperationVerb("POST"));
        operationVerbs.put("put", ScaffoldNameUtils.resolveOperationVerb("PUT"));
        operationVerbs.put("delete", ScaffoldNameUtils.resolveOperationVerb("DELETE"));
        model.put("operationVerbs", operationVerbs);

        boolean useH2Database = context.getOptions().getDatabaseType() == com.example.demo.generator.scaffold.model.ScaffoldOptions.DatabaseType.H2;
        String defaultDatasourceName = ScaffoldNameUtils.deriveDatasourceName(context);
        String databaseName = firstNonBlank(context.getOptions().getDatabaseName(), defaultDatasourceName);
        String databaseHost = firstNonBlank(context.getOptions().getDatabaseHost(), "localhost");
        int databasePort = context.getOptions().getDatabasePort() != null ? context.getOptions().getDatabasePort() : 3306;

        String databaseUsername;
        String databasePassword;
        String databaseDriver;
        String databaseUrl = context.getOptions().getDatabaseUrl();

        if (useH2Database) {
            databaseUsername = firstNonBlank(context.getOptions().getDatabaseUsername(), "sa");
            databasePassword = context.getOptions().getDatabasePassword();
            if (databasePassword == null) {
                databasePassword = "";
            }
            databaseDriver = "org.h2.Driver";
            if (databaseUrl == null || databaseUrl.isBlank()) {
                databaseUrl = String.format("jdbc:h2:mem:%s;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false", databaseName);
            }
        } else {
            databaseUsername = firstNonBlank(context.getOptions().getDatabaseUsername(), "root");
            databasePassword = context.getOptions().getDatabasePassword();
            if (databasePassword == null) {
                databasePassword = "changeMe";
            }
            databaseDriver = firstNonBlank(context.getOptions().getDatabaseDriverClassName(), "com.mysql.cj.jdbc.Driver");
            if (databaseUrl == null || databaseUrl.isBlank()) {
                databaseUrl = String.format("jdbc:mysql://%s:%d/%s?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai", databaseHost, databasePort, databaseName);
            }
        }

        model.put("databaseType", context.getOptions().getDatabaseType());
        model.put("useH2Database", useH2Database);
        model.put("databaseUrl", databaseUrl);
        model.put("databaseHost", databaseHost);
        model.put("databasePort", databasePort);
        model.put("databaseUsername", databaseUsername);
        model.put("databasePassword", databasePassword);
        model.put("databaseName", databaseName);
        model.put("databaseDriverClassName", databaseDriver);

        model.put("applicationClassName", ScaffoldNameUtils.deriveApplicationClassName(context));
        model.put("projectArtifactId", ScaffoldNameUtils.deriveArtifactId(context));
        model.put("projectName", ScaffoldNameUtils.deriveProjectName(context));
        model.put("datasourceName", defaultDatasourceName);
        Map<String, String> packageMap = new HashMap<>();
        packageMap.put("module", context.getModulePackage());
        packageMap.put("controller", context.resolvePackage("controller"));
        packageMap.put("service", context.resolvePackage("service"));
        packageMap.put("serviceImpl", context.resolvePackage("service.impl"));
        packageMap.put("mapper", context.resolvePackage("mapper"));
        packageMap.put("entity", context.resolvePackage("model.entity"));
        packageMap.put("dto", context.resolvePackage("model.dto"));
        packageMap.put("blockchain", context.resolvePackage("integration.fisco"));
        String basePackage = context.getBasePackage();
        packageMap.put("fiscoConfig", basePackage + ".fisco.config");
        packageMap.put("fiscoService", basePackage + ".fisco.service");
        packageMap.put("fiscoServiceImpl", basePackage + ".fisco.service.impl");
        packageMap.put("fiscoModel", basePackage + ".fisco.model");
        model.put("packages", packageMap);
        return model;
    }

    private static String resolveModuleDisplayName(TemplateContext context) {
        String displayName = firstValidLabel(
                context.getOptions().getModuleDescription(),
                ScaffoldNameUtils.humanizeName(context.getModuleSimpleName(), null),
                ScaffoldNameUtils.humanizeName(context.getModuleName(), null),
                context.getClassInfo() != null ? ScaffoldNameUtils.humanizeName(context.getClassInfo().getClassName(), null) : null
        );
        if (displayName == null) {
            displayName = "业务模块";
        }
        return displayName;
    }

    private static String resolveModuleSummary(TemplateContext context, String moduleDisplayName, String tableComment) {
        String summary = firstValidSummary(
                context.getOptions().getModuleDescription(),
                tableComment,
                moduleDisplayName + " 数据管理"
        );
        return summary;
    }

    private static String firstNonBlank(String candidate, String fallback) {
        if (candidate != null) {
            String trimmed = candidate.trim();
            if (!trimmed.isEmpty()) {
                return trimmed;
            }
        }
        return fallback;
    }

    private static List<String> resolvePrimaryKeyFieldNames(TemplateContext context) {
        List<String> primaryColumns = context.getClassInfo().getPrimaryKeyColumns();
        if (primaryColumns.isEmpty()) {
            return List.of();
        }
        return context.getClassInfo().getFieldList().stream()
                .filter(field -> primaryColumns.stream().anyMatch(column -> column.equalsIgnoreCase(field.getColumnName())))
                .map(FieldInfo::getFieldName)
                .collect(Collectors.toList());
    }

    private static List<String> resolveUniqueFieldNames(TemplateContext context) {
        return context.getClassInfo().getFieldList().stream()
                .filter(FieldInfo::isUnique)
                .map(FieldInfo::getFieldName)
                .collect(Collectors.toList());
    }

    private static String firstValidLabel(String... values) {
        if (values == null) {
            return null;
        }
        for (String value : values) {
            String cleaned = cleanLabel(value);
            if (cleaned != null) {
                return cleaned;
            }
        }
        return null;
    }

    private static String firstValidSummary(String... values) {
        if (values == null || values.length == 0) {
            return "业务数据管理";
        }
        for (int i = 0; i < values.length; i++) {
            String value = values[i];
            if (value == null) {
                continue;
            }
            String condensed = value.trim().replaceAll("\\s+", " ");
            if (condensed.isEmpty()) {
                continue;
            }
            if (SQL_LIKE_PATTERN.matcher(condensed).find()) {
                continue;
            }
            if (condensed.length() > 160) {
                condensed = condensed.substring(0, 157) + "...";
            }
            return condensed;
        }
        // last element treated as fallback
        String fallback = values[values.length - 1];
        if (fallback == null || fallback.trim().isEmpty()) {
            return "业务数据管理";
        }
        String condensedFallback = fallback.trim().replaceAll("\\s+", " ");
        if (condensedFallback.length() > 160) {
            condensedFallback = condensedFallback.substring(0, 157) + "...";
        }
        return condensedFallback;
    }

    private static String cleanLabel(String raw) {
        if (raw == null) {
            return null;
        }
        String trimmed = raw.trim();
        if (trimmed.isEmpty()) {
            return null;
        }
        if (SQL_LIKE_PATTERN.matcher(trimmed).find()) {
            return null;
        }
        String humanized = ScaffoldNameUtils.humanizeName(trimmed, trimmed);
        if (humanized == null || humanized.trim().isEmpty()) {
            humanized = trimmed;
        }
        String normalized = humanized.trim().replaceAll("\\s+", " ");
        if (normalized.isEmpty()) {
            return null;
        }
        if (normalized.length() > 80) {
            normalized = normalized.substring(0, 77) + "...";
        }
        return normalized;
    }

    private static String extractTableComment(String rawSql) {
        if (rawSql == null) {
            return null;
        }
        java.util.regex.Matcher matcher = TABLE_COMMENT_PATTERN.matcher(rawSql);
        String comment = null;
        while (matcher.find()) {
            comment = matcher.group(1);
        }
        return comment;
    }

    private static final Pattern SQL_LIKE_PATTERN = Pattern.compile("(?i)\\bcreate\\s+table\\b|\\binsert\\b|\\bupdate\\b|\\bdelete\\b|[`;]");
    private static final Pattern TABLE_COMMENT_PATTERN = Pattern.compile("(?i)comment\\s*=\\s*'([^']*)'");

    static final class ValidationHelper {

        static final ValidationHelper INSTANCE = new ValidationHelper();

        private static final Map<String, String> ANNOTATION_IMPORTS;

        static {
            Map<String, String> imports = new HashMap<>();
            imports.put("NotNull", "javax.validation.constraints.NotNull");
            imports.put("NotBlank", "javax.validation.constraints.NotBlank");
            ANNOTATION_IMPORTS = Collections.unmodifiableMap(imports);
        }

        List<String> collectImports(List<FieldInfo> fields, boolean forCreate) {
            if (fields == null || fields.isEmpty()) {
                return List.of();
            }
            java.util.Set<String> imports = new java.util.LinkedHashSet<>();
            fields.forEach(field -> resolveAnnotations(field, forCreate).forEach(annotation -> {
                String shortName = annotationShortName(annotation);
                String importValue = ANNOTATION_IMPORTS.get(shortName);
                if (importValue != null) {
                    imports.add(importValue);
                }
            }));
            return new java.util.ArrayList<>(imports);
        }

        List<String> resolveAnnotations(FieldInfo field, boolean forCreate) {
            if (field == null) {
                return List.of();
            }
            if (field.isPrimaryKey() || field.isAutoIncrement()) {
                return List.of();
            }
            if (!field.isNullable()) {
                String label = buildLabel(field);
                if (isStringType(field)) {
                    return List.of(String.format("@NotBlank(message = \"%s不能为空\")", label));
                }
                return List.of(String.format("@NotNull(message = \"%s不能为空\")", label));
            }
            return List.of();
        }

        private boolean isStringType(FieldInfo field) {
            String type = field.getFieldClass();
            if (type == null) {
                return false;
            }
            return type.endsWith("String");
        }

        private String buildLabel(FieldInfo field) {
            String comment = field.getFieldComment();
            if (comment != null && !comment.trim().isEmpty()) {
                return comment.trim().replace("\"", "\\\"");
            }
            return ScaffoldNameUtils.humanizeName(field.getFieldName(), field.getFieldName());
        }

        private String annotationShortName(String annotation) {
            String trimmed = annotation.trim();
            int start = trimmed.indexOf('@');
            if (start == -1) {
                return trimmed;
            }
            int end = trimmed.indexOf('(', start);
            if (end == -1) {
                end = trimmed.length();
            }
            return trimmed.substring(start + 1, end);
        }
    }
}
